package ethclient
  
import (
	"crypto/ecdsa"
	"fmt"
	"math/big"

	"gitee.com/thubcc/blockchain/accounts/ecc"
	"gitee.com/thubcc/blockchain/accounts/eip155"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/common/hexutil"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/ethereum/go-ethereum/rlp"
	"github.com/ethereum/go-ethereum/rpc"
	"golang.org/x/net/context"
)

type Config struct {
	Price        *big.Int
	GasLimit     uint64
	ChainID      int64
	Url          string
}

func NewConfig(price *big.Int,cID int64,url string) *Config {	
	if price==nil {
		price = big.NewInt(0x3b9aca00)
	}

	return &Config{
		price,
		6_000_000,
		cID,
		url,
	}
}

type Ext struct {
	Config
	rpc *rpc.Client
	eth *ethclient.Client
	signer eip155.Signer
	e155 *eip155.EIP155
	address common.Address
	nonce uint64
	ctx context.Context  
}

func(config *Config) NewExt(key *ecdsa.PrivateKey) (*Ext, error) {
	ctx := context.Background()
	rpc, err := rpc.DialContext(ctx, config.Url)
	if err != nil {
		return nil,fmt.Errorf("Dial failure %v",err)
	}
	
	c := ethclient.NewClient(rpc)
	
	cid,err := c.ChainID(ctx)
	if err!=nil {
		return nil,fmt.Errorf("Get chain ID failure %v",err)
	}
	if cid.Int64()!=config.ChainID {
		return nil,fmt.Errorf("mismatch chain ID %d",cid.Int64())
	}

	var (
		e155    = eip155.NewEIP155(config.ChainID)
		address common.Address
		signer  eip155.Signer
	)

	if key != nil {
		curve      := ecc.NewSecp256K1()
		keyString  := key.D.Text(16)
		prk,_      := new(big.Int).SetString(keyString,16)
		address     = common.HexToAddress(curve.PrivateKey2Address(prk))
		signer      = e155.NewEIP155Signer(curve,keyString)
	} else {
		address    = common.Address{}
		signer     = nil
	}

	return &Ext{
		*config,
		rpc,
		c,
		signer,
		&e155,
		address,
		0,
		ctx,
	}, nil
}

func(ext *Ext) Nonce() uint64 { return ext.nonce }
func(ext *Ext) FillTx(nonce *uint64, amount *big.Int) (*eip155.Transaction,error) {
	if *nonce == 0 {
		b,err := ext.eth.NonceAt(ext.ctx, ext.address, nil)
		if err != nil {
			return nil,fmt.Errorf("Get Nonce failure %v",err)
		}
		*nonce = b
	}
	ext.nonce = *nonce
	fmt.Println("Nonce",ext.nonce)
	tx := eip155.NewTransaction(ext.nonce,nil,amount,ext.GasLimit,ext.Price,[]byte{})
	return tx,nil
}

func(ext *Ext) SendTx(nonce uint64,to *common.Address,data []byte,value *big.Int) (*common.Hash,error) {
	fmt.Println("Nonce",nonce)
	tx,err := ext.FillTx(&nonce,value)
	if err !=nil {
		return nil,fmt.Errorf("Fill Tx failure, %v",err)
	}
	if to != nil {
		tx.SetTo(*to)
	}
	if len(data)>0 {
		tx.SetData(data)
	}
	hash := ext.e155.HashExt(tx)
	
	Sig := ext.signer.Sign(hash.Bytes())
	tx.R,tx.S,tx.V = Sig.R,Sig.S,Sig.V

	raw,err := rlp.EncodeToBytes(tx)
	if err !=nil {
		return nil,fmt.Errorf("rlp encode failure, %v",err)
	}

	err = ext.rpc.CallContext(ext.ctx,nil, "eth_sendRawTransaction", hexutil.Encode(raw)) 
	var etx types.Transaction
	rlp.DecodeBytes(raw,&etx)
	json,_ := etx.MarshalJSON()
	fmt.Println(string(json))
	rhash := etx.Hash()
	return &rhash,err
}